【SwiftUI】Listの並び替えを実装する
Listで行の並び替えをしたかったので調べました。今回はeditMode
を使用して並び替えする方法を記載しております。
環境
- Xcode 13.3
Listで並び替える
EditButtonを使用する
EditButton()
を使用することで環境変数editMode
が切り替わり、それに伴いList
の編集モードも切り替わります。
コード
import SwiftUI struct ContentView: View { @State private var numbers: [Int] = [1, 2, 3, 4, 5] var body: some View { VStack { List { ForEach(numbers, id: \.self) { number in Text(String(number)) } .onMove(perform: moveRow) .onDelete(perform: removeRow) } EditButton() .padding() } } private func moveRow(from source: IndexSet, to destination: Int) { numbers.move(fromOffsets: source, toOffset: destination) } private func removeRow(from source: IndexSet) { numbers.remove(atOffsets: source) } }
Edit Button()
を使用して、編集モードに切り替えるだけではListの並び替えは行えません。.onMove
とonDelete
を使用することで削除ボタンや並び替えマークが表示されるようになります。
デモ
onMove
func onMove(perform action: ((IndexSet, Int) -> Void)?) -> some DynamicViewContent
動的ビューの要素が移動されたときにSwiftUIが呼び出すクロージャ。クロージャは、動的ビューの基になるデータのコレクションに対するオフセットを表す2つの引数を取ります。アイテムを移動する機能を無効にするには、nilを渡します。
第一引数は、移動元のIndexSet
で、第二引数は、移動先のオフセットInt
になります。その値を使用して、move(fromOffsets:, toOffset:)
を実行します。
公式ドキュメントmove(fromOffsets:toOffset:)の内容がないですが、動き的には関数名を呼んで字の如く第二引数の箇所に移動しています。
onDelete
func onDelete(perform action: Optional<(IndexSet) -> Void>) -> some DynamicViewContent
ビュー内の要素が削除されたときにSwiftUIに実行させるアクション
削除させるコレクション要素に関連するIndexSet
を持っていますので、それを使用して、remove(atOffsets: source)
で要素を削除しています。
EditButtonを使わずに並び替える
上記では、EditButton()
を使用して、編集モードを切り替えましたが、EditButton()
を使用せずとも編集モードは切り替えることが出来ます。
環境変数editMode
を.active
にすることで編集モードに切り替えることが出来ます。
コード
struct ContentView: View { @State private var numbers: [Int] = [1, 2, 3, 4, 5] var body: some View { List { ForEach(numbers, id: \.self) { number in Text(String(number)) } .onMove(perform: moveRow) .onDelete(perform: removeRow) } .environment(\.editMode, .constant(.active)) } private func moveRow(from source: IndexSet, to destination: Int) { numbers.move(fromOffsets: source, toOffset: destination) } private func removeRow(from source: IndexSet) { numbers.remove(atOffsets: source) } }
こちらの実装では.environment
を使用し、 editMode
を.constant(.active)
にすることで常時編集モードにしています。
デモ
削除ボタンを非表示にする
.onDelete
を削除する。
List { ForEach(numbers, id: \.self) { number in Text(String(number)) } .onMove(perform: moveRow) }
または、.deleteDisabled
を使用して、値をtrue
にする。
List { ForEach(numbers, id: \.self) { number in Text(String(number)) } .onMove(perform: moveRow) .onDelete(perform: removeRow) .deleteDisabled(true) }
すると、削除ボタンが表示されなくなりました。
編集モードの時だけ削除ボタンを非表示にする
上記で紹介した方法だと、スワイプアクションの時も削除ボタンが非表示になってしまいます。編集モードの時は削除ボタンは非表示にするけど、スワイプアクション時は表示したい場合はeditMode
の値を見て、処理を分岐させます。
import SwiftUI struct ContentView: View { @Environment(\.editMode) var editMode @State private var numbers: [Int] = [1, 2, 3, 4, 5] var body: some View { VStack { List { ForEach(numbers, id: \.self) { number in Text(String(number)) } .onMove(perform: moveRow) .onDelete(perform: removeRow) .deleteDisabled(isDeleteDisabled) } EditButton() .padding() } } private var isDeleteDisabled: Bool { if editMode?.wrappedValue == .active { return true } return false } private func moveRow(from source: IndexSet, to destination: Int) { numbers.move(fromOffsets: source, toOffset: destination) } private func removeRow(from source: IndexSet) { numbers.remove(atOffsets: source) } }
isDeleteDisabled
で、環境変数editMode
が.active
の時はisDisable
をtrue
にし、そうでなければfalse
を返しています。
private var isDeleteDisabled: Bool { if editMode?.wrappedValue == .active { return true } return false }
その値をdeleteDisabled
に設定することで編集モード中は削除ボタンを非表示にすることが出来ます。
.deleteDisabled(isDeleteDisabled)
デモ
おわりに
今回、UITableView
で行うような処理もListだと少ないコードで書くことが出来るということが分かりました。まだまだ現時点ではUITableView
よりは出来ることは少ないですが、これからの成長を楽しみにしていきたいですね。